﻿/*
 * Created by SharpDevelop.
 * User: issuser
 * Date: 2010-11-5
 * Time: 12:36
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Linq;
using NLite.Collections;
using System.Data;
using System.Collections.Generic;
using NLite.Data.Driver;
using NLite.Data.Internal;

namespace NLite.Data
{


    //public class SqlManager<T> : SqlManager
    //{
    //    public SqlManager() : base(typeof(T).Name) { }

    //    public SqlManager<T> Select(params Expression<Func<T, object>>[] columns)
    //    {
    //        if (columns == null || columns.Length == 0) return this;

    //        foreach (var databaseName in columns)
    //        {
    //            var memberInfo = databaseName.FindProperty();
    //            if (memberInfo != null)
    //                base.Select(memberInfo.Name);
    //        }
    //        return this;
    //    }

    //    public SqlManager<T> Where(Expression<Func<T, bool>> filter)
    //    {
    //        /*
    //        var visitor = new ExpressionVisitor();
    //        visitor.Builder.Filter = visitor.ParseQuery(filter.Body) as FilterOperation;
           
    //        Func<ExpressionType,string> ParseType = type=>
    //            {
    //                 switch (type)
    //                        {
    //                            case ExpressionType.Equal:
    //                                return " == ";
    //                            case ExpressionType.NotEqual:
    //                                return " != ";
    //                            case ExpressionType.GreaterThan: return ">";
    //                            case ExpressionType.GreaterThanOrEqual: return ">=";
    //                            case ExpressionType.LessThan: return "<";
    //                            case ExpressionType.LessThanOrEqual: return "<=";
    //                     default:
    //                         throw new NotSupportedException ();
    //                        }
    //            };
    //        var ops = visitor.Builder;
    //         Func<IOperation,string> NodeHandler = node =>
    //            {
    //                switch (node.NodeType)
    //                {
    //                    case OperationType.Binary:
    //                        var binary = node as BinaryOperation;
    //                        AddParameter(binary.Name, binary.Value);
    //                        return binary.Name + ParseType(binary.Type) + DbConfig.Dialect.NamedPrefix + binary.Name;
    //                    case OperationType.Unary:
    //                       var unary = node as UnaryOperation;
    //                       throw new NotSupportedException();

    //                }
    //                return null;
    //            };

    //         Action<FilterOperation> handler = null;
    //         var sb = new StringBuilder();
    //         handler = con =>
    //            {
    //                LogicType type = LogicType.And;
    //                if (con.Type == ExpressionType.AndAlso)
    //                    type = LogicType.And;
    //                else if (con.Type == ExpressionType.OrElse)
    //                    type = LogicType.Or;

    //                if (con.Left != null)
    //                {
    //                    if (con.Left.NodeType == OperationType.Filter)
    //                    {
    //                        sb.Append("(");
    //                        handler(con.Left as FilterOperation);
    //                        sb.Append(")");
    //                    }
    //                    else
    //                        sb.Append(DbExpression.Filter(NodeHandler(con.Left)).Text);
    //                }
    //                if (con.Right != null)
    //                {
    //                    sb.Append(" ").Append(type.ToString().ToUpper()).Append(" ");
    //                    if (con.Right.NodeType == OperationType.Filter)
    //                    {
    //                        sb.Append("(");
    //                        handler(con.Right as FilterOperation);
    //                        sb.Append(")");
    //                    }
    //                    else
    //                        sb.Append(DbExpression.Filter(type, NodeHandler(con.Right)).Text);
    //                }
    //            };
           

    //        handler(visitor.Builder.Filter );
    //        base.Where(sb.ToString());*/
    //        return this;
    //    }
    //}

    
    //public class Update:QueryBase<UpdateNode>
    //{
    //    public Update(string table)
    //    {
    //        Expression = new UpdateNode(table);
    //    }

    //    public Update Set(string column, string columnValue)
    //    {
    //        Expression.NameValues.Add(new NameValue( new ColumnNode(column,Expression.Name), columnValue));
    //        return this;
    //    }
    //    public Update Where(CriterionNode filter)
    //    {
    //        Expression.Where.AddCriterion(filter, LogicOperator.Or);
    //        return this;
    //    }
    //    public Update And(CriterionNode filter)
    //    {
    //        Expression.Where.AddCriterion(filter, LogicOperator.And);
    //        return this;
    //    }
    //    public Update Or(CriterionNode filter)
    //    {
    //        Expression.Where.AddCriterion(filter, LogicOperator.Or);
    //        return this;
    //    }

    //    public Update Where(string filter)
    //    {
    //        Expression.Where.AddCriterion(Criterion.Native(filter), LogicOperator.Or);
    //        return this;
    //    }
    //    public Update And(string filter)
    //    {
    //        Expression.Where.AddCriterion(Criterion.Native(filter), LogicOperator.And);
    //        return this;
    //    }
    //    public Update Or(string filter)
    //    {
    //        Expression.Where.AddCriterion(Criterion.Native(filter), LogicOperator.Or);
    //        return this;
    //    }
    //}

    public static class SqlManager
    {

        public static IQuery<TTable> From<TTable>(TTable table) where TTable : ITable
        {
            return new InnerQuery<TTable>(table);
        }

        public static IQuery<TTable> From<TTable>() where TTable : ITable, new()
        {
            return new InnerQuery<TTable>(new TTable());
        }

        public static IUpdateQuery<TTable> Update<TTable>() where TTable : ITable, new()
        {
            return new UpdateQuery<TTable>(new TTable());
        }

        public static IUpdateQuery<TTable> Update<TTable>(Func<TTable, ICriterionNode> filter) where TTable : ITable, new()
        {
            return Update<TTable>().Where(filter);
        }

        public static IInsertQuery<TTable> Insert<TTable>() where TTable : ITable, new()
        {
            return new InsertQuery<TTable>(new TTable());
        }

        public static IDeleteQuery<TTable> Delete<TTable>() where TTable : ITable, new()
        {
            return new DeleteQuery<TTable>(new TTable());
        }

        public static IDeleteQuery<TTable> Delete<TTable>(Func<TTable,ICriterionNode> filter) where TTable : ITable, new()
        {
            return Delete<TTable>().Where(filter);
        }
        public static CriterionNode In(this SqlObject col, IQuery q)
        {
            return col.In(q.Node);
        }

       
    }

    public interface IQueryText
    {
        SqlBuilder Result { get; }
        string Sql { get; }
    }
    public interface IQuery : IQueryText
    {
        SelectNode Node { get; }
    }

    public interface IUpdateQuery : IQueryText
    {
    }

    public interface IQuery<TTable> : IQuery
    {
        TTable Table { get; }
        IJoinAs<TTable, TJoinTable> Join<TJoinTable>() where TJoinTable : ITable, new();
        IJoinAs<TTable, TJoinTable> LeftJoin<TJoinTable>() where TJoinTable : ITable, new();
        IJoinAs<TTable, TJoinTable> RightJoin<TJoinTable>() where TJoinTable : ITable, new();
        IJoinAs<TTable, TJoinTable> FullJoin<TJoinTable>() where TJoinTable : ITable, new();

        IQuery<TTable> Where(ICriterionNode criterion);
        IQuery<TTable> Where(Func<TTable, ICriterionNode> callback);
        IQuery<TTable> And(ICriterionNode criterion);
        IQuery<TTable> Or(ICriterionNode criterion);
        IQuery<TTable> And(Func<TTable, ICriterionNode> callback);
        IQuery<TTable> Or(Func<TTable, ICriterionNode> callback);

        IQuery<TTable> Group(params IColumnNode[] columns);
        IQuery<TTable> Group(params Func<TTable, IColumnNode>[] callbacks);

        IQuery<TTable> Having(ICriterionNode criterion);
        IQuery<TTable> Having(Func<TTable, ICriterionNode> callback);

        IQuery<TTable> OrderBy(params OrderItemNode[] items);
        IQuery<TTable> OrderBy(params Func<TTable, OrderItemNode>[] callbacks);

        IQuery<TTable> Select(params IProjectionNode[] projections);
        IQuery<TTable> Select(params  Func<TTable, IProjectionNode>[] callbacks);

        IQuery<TTable> Union(IQuery query);
        IQuery<TTable> Intersect(IQuery query);
        IQuery<TTable> Except(IQuery query);
        IQuery<TTable> Minus(IQuery query);

        IQuery<TTable> Into<TInsertTable>(TInsertTable anotherTable) where TInsertTable : ITable;
        IQuery<TTable> Into(string anotherTable);
        IQuery<TTable> ForUpdate();
        IQuery<TTable> PageSize(int pageSize);
        IQuery<TTable> PageIndex(int pageIndex);
    }

    public interface IUpdateQuery<TTable> : IUpdateQuery
    {
        IUpdateQuery<TTable> Set<TColumn, TType>(TColumn column, TType columnValue) where TColumn : IColumnNode;
        IUpdateQuery<TTable> Set<TColumn, TType>(Func<TTable, TColumn> callback, TType columnValue) where TColumn : IColumnNode;
        IUpdateQuery<TTable> Where(ICriterionNode criterion);
        IUpdateQuery<TTable> Where(Func<TTable, ICriterionNode> callback);
        IUpdateQuery<TTable> And(ICriterionNode criterion);
        IUpdateQuery<TTable> Or(ICriterionNode criterion);
        IUpdateQuery<TTable> And(Func<TTable, ICriterionNode> callback);
        IUpdateQuery<TTable> Or(Func<TTable, ICriterionNode> callback);

    }

    public interface IDeleteQuery<TTable> : IUpdateQuery
    {
        IDeleteQuery<TTable> Where(ICriterionNode criterion);
        IDeleteQuery<TTable> Where(Func<TTable, ICriterionNode> callback);
        IDeleteQuery<TTable> And(ICriterionNode criterion);
        IDeleteQuery<TTable> Or(ICriterionNode criterion);
        IDeleteQuery<TTable> And(Func<TTable, ICriterionNode> callback);
        IDeleteQuery<TTable> Or(Func<TTable, ICriterionNode> callback);

    }

    public interface IInsertQuery<TTable> : IUpdateQuery
    {
        IInsertQuery<TTable> Set<TColumn, TType>(TColumn column, TType columnValue) where TColumn : IColumnNode;
        IInsertQuery<TTable> Set<TColumn, TType>(Func<TTable, TColumn> callback, TType columnValue) where TColumn : IColumnNode;
    }

    public interface IJoinOn<TTable, TJoinTable>
    {
        IQuery<TTable> On(Func<TTable, TJoinTable, ICriterionNode> criterion);
    }

    public interface IJoinAs<TTable, TJoinTable>
    {
        IJoinOn<TTable, TJoinTable> As(string alias);
        IQuery<TTable> On(Func<TTable, TJoinTable, ICriterionNode> criterion);
    }

    public static class QueryExtensions
    {
        public static IDataReader ExecuteReader(this IQuery query)
        {
            return DbConfig.Driver.ExecuteReader(query);
        }
        public static DataTable ExecuteDataTable(this IQuery query)
        {
            var tb = DbConfig.Driver.ExecuteReader(query).ToDataTable(); //DbConfig.Driver.ExecuteDataTable(query);
            tb.TableName = query.Node.Name;
            return tb;
        }

        public static T ExecuteScalar<T>(this IQuery query)
        {
            return DbConfig.Driver.ExecuteScalar<T>(query);
        }

        public static IEnumerable<T> ExecuteScalarList<T>(this IQuery query)
        {
            return DbConfig.Driver.ExecuteScalarList<T>(query);
        }

        public static int Execute(this IUpdateQuery query)
        {
            return DbConfig.Driver.ExecuteNonQuery(query);
        }
    }

    namespace Internal
    {
        internal class InternalCase : Case.IWhen, Case.IThen, Case.IElse
        {
            public CaseNode Inner;
            internal InternalCase(ICriterionNode when)
            {
                Inner = new CaseNode();
                Inner.whens = new List<ICriterionNode>();
                Inner.whens.Add(when);
                Inner.thens = new List<INode>();
            }

            Case.IThen Case.IWhen.Then(INode then)
            {
                Inner.thens.Add(then);
                return this;
            }

            Case.IWhen Case.IThen.When(ICriterionNode when)
            {
                Inner.whens.Add(when);
                return this;
            }

            Case.IElse Case.IThen.Else(INode @else)
            {
                Inner.@else = @else;
                return this;
            }
            public CaseNode End
            {
                get { return Inner; }
            }
        }

        class DataParameter : Node, IDataParameter, IParameterNode
        {
            public NameIdentity Identity { get; private set; }
            public string Name { get; private set; }
            public object Value { get; private set; }

            public DataParameter(NameIdentity identity, string name, object value)
                : base(NodeType.Parameter)
            {
                Identity = identity;
                Name = name;
                Value = value;
            }
            public DataParameter(string name, object value)
                : base(NodeType.Parameter)
            {
                Name = name;
                Value = value;
            }
            public override object Clone()
            {
                return this;
            }
        }

        class CollectionProxy : List<INode>, INode
        {
            public CollectionProxy(IEnumerable<INode> items)
            {
                foreach (var item in items)
                    Add(item);
            }

            public NodeType NodeType
            {
                get { throw new NotImplementedException(); }
            }

            public INode Accept(INodeVisitor visitor)
            {
                throw new NotImplementedException();
            }

            public SqlBuilder Result
            {
                get { throw new NotImplementedException(); }
            }

            public object Clone()
            {
                throw new NotImplementedException();
            }
        }

        class InnerQuery<TTable> : IQuery<TTable> where TTable : ITable
        {
            public SelectNode Node { get; private set; }
            public SqlBuilder Result
            {
                get { return Node.Result; }
            }
            public string Sql
            {
                get { return Node.Result.Text; }
            }

            public TTable Table { get; private set; }

            public override string ToString()
            {
                return Node.ToString();
            }

            public InnerQuery(TTable table)
            {
                Table = table;

                //if(table is SelectNode )
                //    Node =?? 
                Node = new SelectNode(table.TableName, table.TableAlias);
            }

            class JoinProxy<TJoinTable> : IJoinAs<TTable, TJoinTable>, IJoinOn<TTable, TJoinTable> where TJoinTable : ITable
            {
                InnerQuery<TTable> Query;
                string Alias;
                TJoinTable Table;
                JoinType Type;

                public JoinProxy(JoinType type, TJoinTable tb, InnerQuery<TTable> query)
                {
                    Type = type;
                    Query = query;
                    Table = tb;
                }

                public IJoinOn<TTable, TJoinTable> As(string alias)
                {
                    Alias = alias ?? Table.TableAlias;
                    return this;
                }

                public IQuery<TTable> On(Func<TTable, TJoinTable, ICriterionNode> callback)
                {
                    var item = Query.Node.Joins.FirstOrDefault(
                        p => string.Equals(p.Name, Table.TableName, StringComparison.InvariantCultureIgnoreCase)
                        && string.Equals(p.Alias, Table.TableAlias, StringComparison.InvariantCultureIgnoreCase));

                    if (item != null)
                        throw new SqlParseExecption("join table exists.");

                    if (callback != null)
                        Query.Node.Joins.Add(new JoinNode(Type, Table.TableName, Alias, callback(Query.Table, Table)));
                    return Query;
                }
            }


            public IJoinAs<TTable, TJoinTable> Join<TJoinTable>() where TJoinTable : ITable, new()
            {
                return new JoinProxy<TJoinTable>(JoinType.Inner, new TJoinTable(), this);
            }

            public IJoinAs<TTable, TJoinTable> LeftJoin<TJoinTable>() where TJoinTable : ITable, new()
            {
                return new JoinProxy<TJoinTable>(JoinType.Left, new TJoinTable(), this);
            }

            public IJoinAs<TTable, TJoinTable> RightJoin<TJoinTable>() where TJoinTable : ITable, new()
            {
                return new JoinProxy<TJoinTable>(JoinType.Right, new TJoinTable(), this);
            }

            public IJoinAs<TTable, TJoinTable> FullJoin<TJoinTable>() where TJoinTable : ITable, new()
            {
                return new JoinProxy<TJoinTable>(JoinType.Full, new TJoinTable(), this);
            }

            public IQuery<TTable> Where(ICriterionNode criterion)
            {
                Node.Where.AddCriterion(criterion);
                return this;
            }

            public IQuery<TTable> And(ICriterionNode criterion)
            {
                Node.Where.AddCriterion(criterion, LogicOperator.And);
                return this;
            }

            public IQuery<TTable> Or(ICriterionNode criterion)
            {
                Node.Where.AddCriterion(criterion, LogicOperator.Or);
                return this;
            }

            public IQuery<TTable> Group(params IColumnNode[] columns)
            {
                if (columns != null && columns.Length > 0)
                    foreach (var item in columns)
                        Node.Group.Items.Add(item);
                return this;
            }

            public IQuery<TTable> Having(ICriterionNode criterion)
            {
                Node.Having.AddCriterion(criterion);
                return this;
            }

            public IQuery<TTable> OrderBy(params OrderItemNode[] items)
            {
                if (items != null && items.Length > 0)
                    foreach (var item in items)
                        AddOrderBy(item);
                return this;
            }

            public IQuery<TTable> Select(params IProjectionNode[] items)
            {
                if (items != null && items.Length > 0)
                    foreach (var item in items)
                        AddProjection(item);
                return this;
            }

            private void AddProjection(IProjectionNode item)
            {
                Node.Projections.Add(item);
            }


            private void AddOrderBy(OrderItemNode item)
            {
                Node.OrderBy.Items.Add(item);
            }


            public IQuery<TTable> Where(Func<TTable, ICriterionNode> callback)
            {
                if (callback != null)
                    Node.Where.AddCriterion(callback(Table));
                return this;
            }

            public IQuery<TTable> And(Func<TTable, ICriterionNode> callback)
            {
                if (callback != null)
                    Node.Where.AddCriterion(callback(Table));
                return this;
            }

            public IQuery<TTable> Or(Func<TTable, ICriterionNode> callback)
            {
                if (callback != null)
                    Node.Where.AddCriterion(callback(Table), LogicOperator.Or);
                return this;
            }


            public IQuery<TTable> Having(Func<TTable, ICriterionNode> callback)
            {
                if (callback != null)
                    Node.Having.AddCriterion(callback(Table));
                return this;
            }


            public IQuery<TTable> Group(params Func<TTable, IColumnNode>[] items)
            {
                if (items != null && items.Length > 0)
                    return Group(items.Select(p => p(Table)).ToArray());
                return this;
            }

            public IQuery<TTable> OrderBy(params Func<TTable, OrderItemNode>[] items)
            {
                if (items != null)
                    return OrderBy(items.Select(p => p(Table)).ToArray());
                return this;
            }

            public IQuery<TTable> Select(params Func<TTable, IProjectionNode>[] items)
            {
                if (items != null)
                    return Select(items.Select(p => p(Table)).ToArray());
                return this;
            }

            public IQuery<TTable> Union(IQuery query)
            {
                if (query != null && query.Node != null)
                    Node.Collections.Add(new CollectionNode(query.Node, CollectionOperator.Union));
                return this;
            }
            public IQuery<TTable> Intersect(IQuery query)
            {
                if (query != null && query.Node != null)
                    Node.Collections.Add(new CollectionNode(query.Node, CollectionOperator.Intersect));
                return this;
            }
            public IQuery<TTable> Except(IQuery query)
            {
                if (query != null && query.Node != null)
                    Node.Collections.Add(new CollectionNode(query.Node, CollectionOperator.Except));
                return this;
            }
            public IQuery<TTable> Minus(IQuery query)
            {
                if (query != null && query.Node != null)
                    Node.Collections.Add(new CollectionNode(query.Node, CollectionOperator.Minus));
                return this;
            }

            public IQuery<TTable> PageSize(int pageSize)
            {
                Node.PageSize = pageSize;
                return this;
            }
            public IQuery<TTable> PageIndex(int pageIndex)
            {
                Node.PageIndex = pageIndex;
                return this;
            }

            public IQuery<TTable> Into<TInsertTable>(TInsertTable anotherTable) where TInsertTable : ITable
            {
                Node.SetInto(anotherTable.TableName);
                return this;
            }

            public IQuery<TTable> Into(string anotherTable)
            {
                Node.SetInto(anotherTable);
                return this;
            }


            public IQuery<TTable> ForUpdate()
            {
                throw new NotImplementedException();
            }
        }

        class UpdateQuery<TTable> : IUpdateQuery<TTable> where TTable : ITable
        {
            public UpdateNode Node { get; private set; }
            public SqlBuilder Result
            {
                get { return Node.Result; }
            }
            public string Sql
            {
                get { return Node.Result.Text; }
            }
            public TTable Table { get; private set; }
            public override string ToString()
            {
                return Node.ToString();
            }


            public UpdateQuery(TTable table)
            {
                Table = table;
                Node = new UpdateNode(table.TableName);
            }

            public IUpdateQuery<TTable> Set<TColumn, TType>(TColumn column, TType columnValue) where TColumn : IColumnNode
            {
                Node.NameValues.Add(new NameValue(column, columnValue));
                return this;
            }

            public IUpdateQuery<TTable> Set<TColumn, TType>(Func<TTable, TColumn> callback, TType columnValue) where TColumn : IColumnNode
            {
                if (callback != null)
                {
                    Node.NameValues.Add(new NameValue(callback(Table), columnValue));
                }
                return this;
            }

            public IUpdateQuery<TTable> Where(ICriterionNode criterion)
            {
                Node.Where.AddCriterion(criterion);
                return this;
            }

            public IUpdateQuery<TTable> Where(Func<TTable, ICriterionNode> callback)
            {
                if (callback != null)
                    return Where(callback(Table));
                return this;
            }

            public IUpdateQuery<TTable> And(ICriterionNode criterion)
            {
                Node.Where.AddCriterion(criterion);
                return this;
            }

            public IUpdateQuery<TTable> Or(ICriterionNode criterion)
            {
                Node.Where.AddCriterion(criterion, LogicOperator.Or);
                return this;
            }

            public IUpdateQuery<TTable> And(Func<TTable, ICriterionNode> callback)
            {
                if (callback != null)
                    return And(callback(Table));
                return this;
            }

            public IUpdateQuery<TTable> Or(Func<TTable, ICriterionNode> callback)
            {
                if (callback != null)
                    return Or(callback(Table));
                return this;
            }
        }

        class InsertQuery<TTable> : IInsertQuery<TTable> where TTable : ITable
        {
            public InsertNode Node { get; private set; }
            public SqlBuilder Result
            {
                get { return Node.Result; }
            }
            public string Sql
            {
                get { return Node.Result.Text; }
            }
            public TTable Table { get; private set; }
            public override string ToString()
            {
                return Node.ToString();
            }
            public InsertQuery(TTable table)
            {
                Table = table;
                Node = new InsertNode(table.TableName);
            }

            public IInsertQuery<TTable> Set<TColumn, TType>(TColumn column, TType columnValue) where TColumn : IColumnNode
            {
                Node.NameValues.Add(new NameValue(column, columnValue));
                return this;
            }

            public IInsertQuery<TTable> Set<TColumn, TType>(Func<TTable, TColumn> callback, TType columnValue) where TColumn : IColumnNode
            {
                if (callback != null)
                    return Set(callback(Table), columnValue);
                return this;
            }
        }

        class DeleteQuery<TTable> : IDeleteQuery<TTable> where TTable : ITable
        {
            public DeleteNode Node { get; private set; }
            public SqlBuilder Result
            {
                get { return Node.Result; }
            }
            public string Sql
            {
                get { return Node.Result.Text; }
            }
            public TTable Table { get; private set; }
            public override string ToString()
            {
                return Node.ToString();
            }
            public DeleteQuery(TTable table)
            {
                Table = table;
                Node = new DeleteNode(table.TableName);
            }


            public IDeleteQuery<TTable> Where(ICriterionNode criterion)
            {
                Node.Where.AddCriterion(criterion);
                return this;
            }

            public IDeleteQuery<TTable> Where(Func<TTable, ICriterionNode> callback)
            {
                if (callback != null)
                    return Where(callback(Table));
                return this;
            }

            public IDeleteQuery<TTable> And(ICriterionNode criterion)
            {
                Node.Where.AddCriterion(criterion);
                return this;
            }

            public IDeleteQuery<TTable> Or(ICriterionNode criterion)
            {
                Node.Where.AddCriterion(criterion, LogicOperator.Or);
                return this;
            }

            public IDeleteQuery<TTable> And(Func<TTable, ICriterionNode> callback)
            {
                if (callback != null)
                    return And(callback(Table));
                return this;
            }

            public IDeleteQuery<TTable> Or(Func<TTable, ICriterionNode> callback)
            {
                if (callback != null)
                    return Or(callback(Table));
                return this;
            }
        }
    }
}
